package wsdc.app.main.v2;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SniHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import wsdc.app.main.U;
import wsdc.app.main.v1.CertConfig;

import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 底层网络api
 */
@Component
@Slf4j
public class HttpProxyBaseApi {
    public static EventLoopGroup work = new NioEventLoopGroup(40);

    @Autowired
    CertConfig certConfig;

    PKCSCertificateManager pkcsCertificateManager;

    @EventListener(classes = ApplicationReadyEvent.class)
    public void start() {
        pkcsCertificateManager = new PKCSCertificateManager(certConfig.getDir());
        startHttpProxyServer(certConfig.getProxyPort());
    }

    //  记录之前访问的数据 在代理的时候
    Map<String, ByteArrayOutputStream> cache = new ConcurrentHashMap<>();

    public ChannelFuture startHttpProxyServer(int port) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(work, work)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //  这里需要主动调用一下 不然后面找不到地址
                        InetSocketAddress remote = ch.remoteAddress();
                        String hostName = remote.getHostName();
                        log.info("HttpProxyServer initChannel 访问地址 {}", hostName);

                        //  这里的handler仅仅只是用于和服务器的 connect 方法 进行连接 告诉服务器需要访问的位置
                        //  服务器就与之进行连接
                        //  connect 同get post 一样 是一个基本的http请求方式
                        ch.pipeline()
                                //  本地tcp客户端建立好连接之前 存储数据 (针对 非 connect方法)
                                //  http+get 就不一定有connect方法 需要重放流量
                                .addLast("v0", new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                        ByteBuf b = (ByteBuf) msg;

                                        int index = b.readerIndex();
                                        byte[] buf = ProxyUtils.readBuf(b).toByteArray();
                                        String key = ctx.channel().hashCode() + "";
                                        //ByteArrayOutputStream byteArrayOutputStream = cache.get(key);
                                        ByteArrayOutputStream byteArrayOutputStream = ctx.channel().attr(ContextKey.os).get();
                                        if (byteArrayOutputStream == null) {
                                            byteArrayOutputStream = new ByteArrayOutputStream();
                                            //cache.put(key, byteArrayOutputStream);
                                            ctx.channel().attr(ContextKey.os).set(byteArrayOutputStream);
                                        }

                                        byteArrayOutputStream.writeBytes(buf);
                                        b.readerIndex(index);
                                        super.channelRead(ctx, msg);
                                    }
                                })
                                //  基础的http报文解析
                                .addLast("v1", new HttpRequestDecoder())
                                .addLast("v2", new HttpResponseEncoder())
                                //  读取连接的报文信息 然后进行连接
                                .addLast("v3", new SimpleChannelInboundHandler<Object>() {
                                    @Override
                                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                        cause.printStackTrace();
                                        ctx.close();
                                        log.info("HttpProxyBaseApi exceptionCaught 异常 host = {} port = {}", cause);
                                    }

                                    @Override
                                    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
                                        //  数据格式就一行
                                        //  CONNECT www.baidu.com:80
                                        //  尽管有通用的端口 这里还是会携带 因为这里是不知道 你请求的是http还是https
                                        //  CONNECT 方法是一段明文
                                        if (msg instanceof HttpRequest) {
                                            HttpRequest req1 = (HttpRequest) msg;
                                            String name = req1.method().name();

                                            log.info("HttpProxyBaseApi channelRead0 访问地址12122 {}", req1.uri());
                                            ctx.channel().attr(ContextKey.g).set(req1.uri());

                                            String key = ctx.channel().hashCode() + "";
                                            ByteArrayOutputStream bs = ctx.channel().attr(ContextKey.os).get();
                                            byte[] bytes = bs.toByteArray();
                                            if (ctx.pipeline().toMap().containsKey("v0")) {
                                                ctx.pipeline().remove("v0");
                                                ctx.pipeline().remove("v1");
                                                ctx.pipeline().remove("v2");
                                                ctx.pipeline().remove("v3");
                                            }
                                            ChannelPipeline pipeline = ctx.pipeline();

                                            if ("CONNECT".equalsIgnoreCase(name)) {
                                                //  connect 方法不管
                                                DefaultHttpResponse res = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200,"Connection Established"));
                                                res.headers()
                                                        .set("Connection","keep-Alive");

                                                ByteBuf buffer = Unpooled.buffer();
                                                buffer.writeBytes("HTTP/1.1 200 Connection established\n\n".getBytes());
                                                ctx.channel().writeAndFlush(buffer);

                                                //  先回复内容 然后在加上处理器

                                                //  只有明确表示访问https的 就是:443
                                               if(U.ssl(req1.uri())){
                                                   // 添加SNI处理器，支持动态证书加载   务必让代理成功的报文先返回了 在加上这些处理器
                                                   pipeline.addLast("v5",new SniHandler(new DynamicSniMapper(pkcsCertificateManager)));
                                               }
                                                // 添加HTTP编解码器
                                                pipeline.addLast("v6",new HttpServerCodec());
                                                //  务必加上聚合器 否则会出现 保证v8获得的结果一定是FullHttpRequest 表示头加上body
                                                pipeline.addLast("v7",new HttpObjectAggregator(1024*1024*10));
                                                // 添加业务处理器
                                                pipeline.addLast("v8",new HttpServerHandler().proxyUrl(req1.uri()));
                                            } else {
                                                //  只有一种情况没有connect方法 就是 http + GET 这对组合可能直接发送get请求，其他情况则一律走connect方法
                                                // 添加SNI处理器，支持动态证书加载
                                                //pipeline.addLast("v5",new SniHandler(new DynamicSniMapper(pkcsCertificateManager)));
                                                // 添加HTTP编解码器
                                                pipeline.addLast("v6",new HttpServerCodec());
                                                pipeline.addLast("v7",new HttpObjectAggregator(1024*1024*10));
                                                // 添加业务处理器
                                                pipeline.addLast("v8",new HttpServerHandler());
                                                if(bytes.length > 0){
                                                    //  如果是其他方法过来的 那么需要将流量重新从顶部开始写入一次  因为是正常请求
                                                    ChannelHandler v6 = pipeline.get("v6");
                                                    ByteBuf buf = Unpooled.wrappedBuffer(bytes);
                                                    ((ChannelInboundHandlerAdapter) v6).channelRead(ctx, buf);
                                                    //buf.release();
                                                }
                                            }
                                        }
                                    }
                                });
                    }
                });

        //  监听端口
        ChannelFuture future = serverBootstrap.bind(port);
        log.info("HttpProxyBaseApi start 启动代理服务器  port = 31995");
        return future;
    }
}
